// (C) Andreas Kaiser 2008

#include <util/crc16.h>
#include "config.h"
#include "timer.h"
#include "rfm01.h"

/*
 * Required:
 *	RFM_SOFTWARE	0 / 1 (hardware/software SPI)
 *	RFM_BUFFER	buffer size
 *	RFM_BAND	433 / 868
 *	RFM_CLOCKOUT	0 / 1...MHz
 *
 *	RFM_SCK_PORT
 *	RFM_SCK_PIN
 *	RFM_MISO_PORT
 *	RFM_MISO_PIN
 *	RFM_MOSI_PORT
 *	RFM_MOSI_PIN
 *	RFM_SS_PORT
 *	RFM_SS_PIN
 *
 * Optional:
 *	RFM_IRQ_PORT
 *	RFM_IRQ_PIN
 *	RFM_IRQ_NAME	INT0/1/2
 */

#if RFM_CLOCKOUT
  #define DefaultCFG	(RF01CFG_EnableCrystal | RF01CFG_CrystalLoad(12.5))
#else
  #define DefaultCFG	(RF01CFG_EnableCrystal | RF01CFG_CrystalLoad(12.5) | RF01CFG_DisableClockOutput)
#endif

//-----------------------------------------------------------------------------

#define SCK_INIT()	do{ SCK_LO(); setBits(DDR(RFM_SCK_PORT), RFM_SCK_PIN); }while(0)
#define SCK_LO()	clrBits(PORT(RFM_SCK_PORT),  RFM_SCK_PIN)
#define SCK_HI()	setBits(PORT(RFM_SCK_PORT),  RFM_SCK_PIN)

#define MOSI_INIT()	setBits(DDR(RFM_MOSI_PORT),  RFM_MOSI_PIN)
#define MOSI_LO()	clrBits(PORT(RFM_MOSI_PORT), RFM_MOSI_PIN)
#define MOSI_HI()	setBits(PORT(RFM_MOSI_PORT), RFM_MOSI_PIN)

#define MISO_INIT()	clrBits(DDR(RFM_MISO_PORT),  RFM_MISO_PIN)
#define MISO()		tstBits(PIN(RFM_MISO_PORT),  RFM_MISO_PIN)

#define SS_INIT()	do{ SS_HI(); setBits(DDR(RFM_SS_PORT), RFM_SS_PIN); }while(0)
#define SS_LO()		clrBits(PORT(RFM_SS_PORT),   RFM_SS_PIN)
#define SS_HI()		setBits(PORT(RFM_SS_PORT),   RFM_SS_PIN)

#ifndef EIMSK
  #define EIMSK GICR
#endif    
#define IRQ_INIT()	do{ clrBits(DDR(RFM_IRQ_PORT),  RFM_IRQ_PIN); \
			    setBits(PORT(RFM_IRQ_PORT), RFM_IRQ_PIN); \
			}while(0)
#define IRQ_ON()	setBits(EIMSK, 1<<RFM_IRQ_NAME)
#define IRQ_OFF()	clrBits(EIMSK, 1<<RFM_IRQ_NAME)

//-----------------------------------------------------------------------------

static volatile uint8_t		error;	// overflow
static uint16_t			crc;

volatile uint8_t		rfm_buffer[RFM_BUFFER];
volatile uint8_t		rfm_length;

//-----------------------------------------------------------------------------
#if 0

void
rfm_decode(uint16_t status)
{
    PRINTF("status %04X ", status);
    if (status & RF01STAT_FFIT)  PRINTF(" FFIT");
    if (status & RF01STAT_FFOV)  PRINTF(" FFOV");
    if (status & RF01STAT_WKUP)  PRINTF(" WKUP");
    if (status & RF01STAT_LBD)   PRINTF(" LBD");
    if (status & RF01STAT_FFEM)  PRINTF(" FFEM");
    if (status & RF01STAT_DRSSI) PRINTF(" DRSSI");
    if (status & RF01STAT_DQD)   PRINTF(" DQD");
    if (status & RF01STAT_CRL)   PRINTF(" CRL");
    if (status & RF01STAT_ATGL)  PRINTF(" ATGL");
    if (status & RF01STAT_ASAME) PRINTF(" ASAME");
    PRINTF("\n");
}

#endif
//-----------------------------------------------------------------------------
#if RFM_SOFTWARE

static void
spi_init(void)
{
}

static uint16_t
spi(uint16_t w, uint8_t n)
{
    uint16_t r = 0;
    for (uint8_t i = 0; i < n; ++i) {
	if (w & 0x8000)
	    MOSI_HI();
	else
	    MOSI_LO();
	r <<= 1;
	if (MISO())
	    r |= 1;
	SCK_HI();
	w <<= 1;
	_delay_us(5);
	SCK_LO();
	_delay_us(5);
    }
    return r;
}

static uint8_t
spib(uint8_t b)
{
    return spi(b, 8);
}

static uint16_t
spiw(uint16_t w)
{
    return spi(w, 16);
}

//-----------------------------------------------------------------------------
#else //!RFM_SOFTWARE

static void
spi_init(void)
{
    // SPI clock <= XTAL/4 for FIFO access
#if F_OSC > 10000000
    // clk/8
    SPCR = 0<<SPIE | 1<<SPE | 0<<DORD | 1<<MSTR | 0<<CPOL | 0<<CPHA | 0<<SPR1 | 1<<SPR0;
    SPSR = 1<<SPI2X;
#elif F_OSC > 5000000
    // clk/4
    SPCR = 0<<SPIE | 1<<SPE | 0<<DORD | 1<<MSTR | 0<<CPOL | 0<<CPHA | 0<<SPR1 | 0<<SPR0;
    SPSR = 0<<SPI2X;
#else
    // clk/2
    SPCR = 0<<SPIE | 1<<SPE | 0<<DORD | 1<<MSTR | 0<<CPOL | 0<<CPHA | 0<<SPR1 | 0<<SPR0;
    SPSR = 1<<SPI2X;
#endif
}

uint8_t
spib(uint8_t b)
{
    SPDR = b;
    while (!(SPSR & (1<<SPIF)))
	;
    return SPDR;
}

uint16_t
spiw(uint16_t w)
{
    uint16_t r = spib(w >> 8) << 8;
    return r | spib(w);
}

#endif
//-----------------------------------------------------------------------------

static uint16_t
spicmd(uint16_t w)
{
    SS_LO();
    uint16_t r = spiw(w);
    SS_HI();
    return r;
}

void
rfm_init(uint16_t frequency, uint16_t clockout)
{
    IRQ_OFF();
    SS_INIT();
    SCK_INIT();
    MOSI_INIT();
    MISO_INIT();
    IRQ_INIT();
    spi_init();
    
    _delay_ms(100);

    spicmd(0x0000);

    rfm_cmd(RF01LBCLK | clockout | RF01LBCLK_Voltage(4.5));
#if RFM_BAND == 868
    rfm_cmd(RF01CFG | RF01CFG_Frequency868 | DefaultCFG | RF01CFG_Bandwidth200);
#elif RFM_BAND == 433
    rfm_cmd(RF01CFG | RF01CFG_Frequency433 | DefaultCFG | RF01CFG_Bandwidth200);
#else
    #error RFM_BAND?
#endif
    rfm_cmd(RF01FREQ | frequency);
    rfm_cmd(RF01AFC | RF01AFC_WhenReceiving | RF01AFC_Medium | RF01AFC_FineMode |
	RF01AFC_EnableOffsetRegister | RF01AFC_EnableOffsetCalculation);
    rfm_cmd(RF01FILT | RF01FILT_FastClockRecovery | RF01FILT_DigitalFilter | RF01FILT_DQD(3));
    rfm_cmd(RF01RCTL | RF01RCTL_DisableRX);
}

uint16_t
rfm_cmd(uint16_t w)
{
    //PRINTF("cmd %04X\n", w);
    IRQ_OFF();
    uint16_t r = spicmd(w);
    IRQ_ON();
    return r;
}

uint16_t
rfm_status(void)
{
    IRQ_OFF();
    uint16_t r = spicmd(RF01STAT);
    IRQ_ON();
    return r;
}

int
rfm_error(void)
{
    if (error)
	return RFM_OVERRUN;
    if (!(rfm_status() & RF01STAT_DRSSI))
	return RFM_NOSIGNAL;
    return 0;
}

//-----------------------------------------------------------------------------

void
rfm_rx_start(uint16_t ctrl)
{
    rfm_cmd(RF01RCTL | RF01RCTL_VDI_CRL | ctrl | RF01RCTL_EnableRX);
    rfm_cmd(RF01FIFO | RF01FIFO_Level(8) | RF01FIFO_SyncPattern);
    rfm_cmd(RF01FIFO | RF01FIFO_Level(8) | RF01FIFO_SyncPattern |
	    RF01FIFO_EnableAfterSync | RF01FIFO_EnableFIFO);
    crc = ~0;
    error = 0;
    rfm_length = 0;
}

int
rfm_rx_stop(void)
{
    int r = rfm_error();
    if (r == 0 && crc != 0)
	r = RFM_CRCFAIL;
    rfm_cmd(RF01RCTL | RF01RCTL_DisableRX);
    return r;
}

//-----------------------------------------------------------------------------

ISR(RFM_IRQ_VECTOR)
{
    SS_LO();
    uint16_t status = spiw(RF01STAT);
    if (status & RF01STAT_FFIT) {
	uint8_t data = spib(0);
	if (rfm_length < RFM_BUFFER) {
	    rfm_buffer[rfm_length++] = data;
	    crc = _crc16_update(crc, data);
	} else
	    error = 1;
    }
    SS_HI();

    if (status & RF01STAT_FFOV)
	error = 1;
}
